﻿using aier.dataaccess.model;
using Microsoft.Xrm.Sdk;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace aier.dataaccess.dapper
{
    public static class XrmSqlBuilderEx
    {
        public static Entity AsCrmEntity<T>(this T model) where T : class
        {
            if ((model == null || model is Entity))
            {
                return null;
            }

            bool isXrmEntity = true;
            Type modelType = model.GetType();
            isXrmEntity = modelType.IsDefined(typeof(XrmEntityAttribute), false);
            if (isXrmEntity) //实体类是否应用XRM属性
            {
                return ConvertEntity<T>(model, modelType);
            }
            else
                return null;
        }

        public static IEnumerable<Entity> AsCrmEntities<T>(this IEnumerable<T> source)
        {
            if ((source == null || source is List<Entity>))
            {
                yield break;
            }

            bool isXrmEntity = true;
     
            foreach (T model in source)
            {
                Type modelType = model.GetType();
                isXrmEntity = modelType.IsDefined(typeof(XrmEntityAttribute), false);
                if (isXrmEntity) //实体类是否应用XRM属性
                {
                    yield return ConvertEntity<T>(model, modelType);
                }
                else
                {
                    yield break;
                }
            }
        }

        private static Entity ConvertEntity<T>(T model, Type modelType)
        {
            PropertyInfo[] pInfos = null;
            bool isXrmField = true;
            XrmEntityAttribute xrmEntityAttr = modelType.GetCustomAttribute<XrmEntityAttribute>();
            Entity crmEntity = new Entity(xrmEntityAttr.EntityName);
            if (pInfos == null)
                pInfos = modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var pitem in pInfos)
            {
                isXrmField = pitem.IsDefined(typeof(XrmFieldMappingAttribute), false);
                if (!isXrmField) //实体属性是否应用XRM属性
                {
                    continue;
                }
                else
                {
                    XrmFieldMappingAttribute xrmFieldMappingAttr = pitem.GetCustomAttribute<XrmFieldMappingAttribute>();
                    if (xrmFieldMappingAttr.CrmType == XrmAttributeType.UniqueIdentifier)
                    {
                        var fieldGuidValue = pitem.GetPropertyGuidValue(model);
                        if (!xrmFieldMappingAttr.AllowEmpty)
                        {
                            if (fieldGuidValue != Guid.Empty)
                            {
                                if (xrmEntityAttr.PrimaryKeyFieldName == xrmFieldMappingAttr.AttributeName)
                                    crmEntity.Id = fieldGuidValue;
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldGuidValue);
                            }
                        }
                        else
                        {
                            if (xrmEntityAttr.PrimaryKeyFieldName == xrmFieldMappingAttr.AttributeName)
                                crmEntity.Id = fieldGuidValue;
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldGuidValue);
                        }
                    }
                    else if (xrmFieldMappingAttr.CrmType == XrmAttributeType.String)
                    {
                        var fieldStringValue = pitem.GetPropertyStringValue(model);



                        if (!xrmFieldMappingAttr.AllowEmpty)
                        {
                            if (!string.IsNullOrWhiteSpace(fieldStringValue))
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldStringValue);
                        }
                        else
                        {
                            if (!string.IsNullOrWhiteSpace(fieldStringValue))
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldStringValue);
                            else
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, string.Empty);
                        }

                    }
                    else if (xrmFieldMappingAttr.CrmType == XrmAttributeType.DateTime)
                    {
                        var fieldDateValue = pitem.GetPropertyDateValue(model);
                        if(fieldDateValue.HasValue)
                        {
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldDateValue);
                        }
                    }
                    else if(xrmFieldMappingAttr.CrmType == XrmAttributeType.Int)
                    {
                        var fieldIntValue = pitem.GetPropertyIntValue(model);
                        if (fieldIntValue != -1)
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldIntValue);
                    }
                    else if(xrmFieldMappingAttr.CrmType == XrmAttributeType.Boolean)
                    {
                        var fieldBoolValue = pitem.GetPropertyBoolValue(model);
                        if (fieldBoolValue!=null) {
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, fieldBoolValue);
                        }           
                    }
                    else if (xrmFieldMappingAttr.CrmType == XrmAttributeType.XrmOptionSetValue)
                    {
                        var fieldIntValue = pitem.GetPropertyIntValue(model);
                        if (fieldIntValue == -1) continue;

                        if (!xrmFieldMappingAttr.AllowEmpty)
                        {
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, new OptionSetValue(fieldIntValue));
                        }
                        else
                        {
                            crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, new OptionSetValue(fieldIntValue));
                        }
                    }
                    else if (xrmFieldMappingAttr.CrmType == XrmAttributeType.EntityReference)
                    {
                        bool isXrmEntityReference = pitem.IsDefined(typeof(XrmReferenceTypeAttribute), false);
                        if (!isXrmEntityReference) continue;
                        var xrmReferenceTypeAttr = pitem.GetCustomAttribute<XrmReferenceTypeAttribute>();

                        var fieldGuidValue = pitem.GetPropertyGuidValue(model);
                        if (fieldGuidValue == Guid.Empty) continue;
                        EntityReference entityReference = new EntityReference(xrmReferenceTypeAttr.EntityName, fieldGuidValue);
                        var referenceValueField = pInfos.GetPropertyInfoByName(xrmReferenceTypeAttr.ReferenceValueField);
                        if (referenceValueField != null)
                        {
                            var referenceValue = referenceValueField.GetPropertyStringValue(model);
                            entityReference.Name = referenceValue;
                        }
                        else
                        {
                            entityReference.Name = string.Empty;
                        }
                        crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, entityReference);
                    }
                    else if(xrmFieldMappingAttr.CrmType == XrmAttributeType.ActivityParty)
                    {
                        bool isActivityPartyAttribute = pitem.IsDefined(typeof(ActivityPartyAttribute), false);
                        if (!isActivityPartyAttribute) continue;
                        var activityPartyAttribute = pitem.GetCustomAttribute<ActivityPartyAttribute>();
                        var fieldEntitieIds = pitem.GetPropertyIEnumerableValue(model);

                        var entities = fieldEntitieIds.Select<Guid, Entity>(entiyid =>
                        {
                            Entity activityParty = new Entity("activityparty");
                            activityParty["partyid"] = new EntityReference(activityPartyAttribute.EntityName, entiyid);
                            return activityParty;
                        });

                        if (!xrmFieldMappingAttr.AllowEmpty)
                        {
                            if (entities != null && entities.Count() > 0)
                            {
                                EntityCollection entityCollection = new EntityCollection(entities.ToList());
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, entityCollection);
                            }
                        }
                        else {
                            if (entities != null && entities.Count() > 0)
                            {
                                EntityCollection entityCollection = new EntityCollection(entities.ToList());
                                crmEntity.Attributes.Add(xrmFieldMappingAttr.AttributeName, entityCollection);
                            }
                            else {
                                throw new Exception($"{xrmFieldMappingAttr?.AttributeName}此属性必须有赋值");
                            }
                        }
                    }
               
                }
            }
            return crmEntity;
        }

        /// <summary>
        /// 反射取得int属性
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <returns></returns>
        private static int GetPropertyIntValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            int fieldIntValue = -1;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldIntValue = Convert.ToInt32(propValue);
            return fieldIntValue;
        }

        /// <summary>
        /// 反射取得string属性
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <returns></returns>
        private static string GetPropertyStringValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            string fieldStringValue = string.Empty;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldStringValue = propValue.ToString();
            return fieldStringValue;
        }

        /// <summary>
        /// 反射取得datetime属性
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <returns></returns>
        private static DateTime? GetPropertyDateValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            DateTime? fieldDateValue = null;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldDateValue = (DateTime?)propValue;
            return fieldDateValue;
        }


        private static Boolean? GetPropertyBoolValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            Boolean? fieldBooleanValue = null;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldBooleanValue = Convert.ToBoolean(propValue);
            return fieldBooleanValue;
        }

        /// <summary>
        /// 反射取得guid属性
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <returns></returns>
        private static Guid GetPropertyGuidValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            Guid fieldGuidValue = Guid.Empty;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldGuidValue = Guid.Parse(propValue.ToString());
            return fieldGuidValue;
        }


        /// <summary>
        /// 反射IEnumerable<Guid>
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <returns></returns>
        private static IEnumerable<Guid> GetPropertyIEnumerableValue(this PropertyInfo propInfo, object objInstace)
        {
            object propValue = null;
            IEnumerable<Guid> fieldEntities = null;
            if (propInfo.TryPropertyValue(objInstace, ref propValue))
                fieldEntities = propValue as IEnumerable<Guid>;
            return fieldEntities ?? throw new  ArgumentException("类型必须是:  IEnumerable<Guid>");
        }


        /// <summary>
        /// 安全取得实体属性值
        /// </summary>
        /// <param name="propInfo"></param>
        /// <param name="objInstace"></param>
        /// <param name="propValue"></param>
        /// <returns></returns>
        private static bool TryPropertyValue(this PropertyInfo propInfo, object objInstace, ref object propValue)
        {
            bool flag = false;
            if (objInstace != null)
            {
                propValue = propInfo.GetValue(objInstace);
                if (propValue != null)
                    flag = true;
            }
            return flag;
        }

        /// <summary>
        /// 根据实体属性名称查找属性
        /// </summary>
        /// <param name="propInfos"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private static PropertyInfo GetPropertyInfoByName(this PropertyInfo[] propInfos, string propertyName)
        {
            var linq = from p in propInfos
                       where p.Name.Equals(propertyName)
                       select p;
            return linq.FirstOrDefault();
        }
    }
}
